package model.symbol;

import event.SymbolChangeEvent;
import event.ValueChangeEvent;
import model.code.*;
import model.code.line.*;

/**
 * This class is for members of {@link model.code.Constants}.
 *
 * @author Neil Dickson
 */
public class Constant extends Rowable implements Symbol, SingleLine.LineObserver, ScopeObserver {
	private final long	id;

	private boolean		isEvaluating;
	private Immediate	immediateValue;

	/**
	 * This is because Java can't do virtual static functions
	 */
	public static final Constant	DUMMY = new Constant("","","");

	public static final int	NAME_INDEX			= 0;
	public static final int	VALUE_INDEX			= 1;
	public static final int	DESCRIPTION_INDEX	= 2;
	public static final int	NUM_STRINGS			= 3;

	public Constant(String name, String description, String value) {
		this(name,description,value,null,0);
	}
	public Constant(String name, String description, String value, CodeFileChunk parent, int row) {
		super(NUM_STRINGS,parent,CodeFileChunk.LOCATION_CONSTANTS_MEMBERS,row);
		id = Scope.getUniqueID();
		isEvaluating = false;
		immediateValue = null;
		strings[NAME_INDEX] = new NameLine(name.toCharArray(),parent,CodeFileChunk.LOCATION_CONSTANTS_MEMBERS,(row<<16)+NAME_INDEX,this);
		strings[VALUE_INDEX] = new ValueLine(value.toCharArray(),parent,CodeFileChunk.LOCATION_CONSTANTS_MEMBERS,(row<<16)+VALUE_INDEX);
		strings[DESCRIPTION_INDEX] = new SingleLine(description.toCharArray(),parent,CodeFileChunk.LOCATION_CONSTANTS_MEMBERS,(row<<16)+DESCRIPTION_INDEX);
		// Don't add observers for external include files
		if (parent==null || parent.getFile()==null || !parent.getFile().isExternal()) {
			getValue().addObserver(this);
		}
	}

	public Constant(String name, String description, String value, boolean isPositionIndependent) {
		this(name,description,value);
	}
	public Constant(String name, String description, String value, boolean isPositionIndependent,boolean isRedefinable) {
		this(name,description,value);
	}

	public Constants getParent() {
		return (Constants)getName().getParent();
	}

	public NameLine getName() {
		return (NameLine)strings[NAME_INDEX];
	}

	public SingleLine getDescription() {
		return strings[DESCRIPTION_INDEX];
	}

	public ValueLine getValue() {
		return (ValueLine)strings[VALUE_INDEX];
	}

	public boolean isFunctionConstant() {
		return getValue().getNumReferences()==1 && getValue().getReferences()[0] instanceof Function;
	}

	public long getID() {
		return id;
	}

	public String toString() {
		return getName().toString();
	}

	public Constant copy(CodeFileChunk parent,int row) {
		return new Constant(getName().toString(),getDescription().toString(),getValue().toString(),parent,row);
	}

	public Immediate getImmediate() {
		if (isEvaluating) {
			return null;
		}
		if (immediateValue==null) {
			evaluateIfNotAlready();
		}
		return immediateValue;
	}

	public void evaluateIfNotAlready() {
		synchronized (this) {
			if (isEvaluating) {
				return;
			}
			isEvaluating = true;
		}
		Immediate enumValue = null;
		if (getValue().length()==0) {
			Constants parent = getParent();
			if (parent!=null && parent.getIncrement().length()!=0) {
				Object objIncrement = parent.getIncrement().evaluate();
				if (objIncrement!=null && objIncrement instanceof Immediate) {
					Immediate increment = (Immediate)objIncrement;
					if (increment.relocation!=null || increment.diffRelocation!=null) {
						if (parent.getIncrement().getNumErrors()==0) {
							parent.getIncrement().addError(new SyntaxError(parent.getIncrement(),0,parent.getIncrement().length(),"The increment field must be either a constant without offsets or \"shl\" or \"shr\"."));
						}
					}
					else {
						int thisIndex = parent.getMembers().indexOf(this);
						int lastDefinedIndex = thisIndex-1;
						while (lastDefinedIndex>=0 && parent.getMembers().get(lastDefinedIndex).getValue().length()==0) {
							--lastDefinedIndex;
						}
						if (lastDefinedIndex<0) {
							if (!increment.isDouble) {
								enumValue = new Immediate(increment.constant*thisIndex);
							}
							else {
								enumValue = new Immediate(Double.doubleToLongBits(Double.longBitsToDouble(increment.constant)*thisIndex),true);
							}
						}
						else {
							Immediate lastDefined = parent.getMembers().get(lastDefinedIndex).getImmediate();
							if (lastDefined==null) {
								if (getValue().getNumErrors()==0) {
									getValue().addError(new SyntaxError(getValue(),0,getValue().length(),"The last defined value isn't valid, and this follows it in the enumeration, so it also has no value."));
								}
							}
							else {
								if (!increment.isDouble) {
									enumValue = new Immediate(lastDefined.constant+increment.constant*(thisIndex-lastDefinedIndex),lastDefined.relocation,lastDefined.diffRelocation);
								}
								else {
									enumValue = new Immediate(Double.doubleToLongBits(Double.longBitsToDouble(lastDefined.constant)+Double.longBitsToDouble(increment.constant)*(thisIndex-lastDefinedIndex)),lastDefined.relocation,lastDefined.diffRelocation,true);
								}
							}
						}
					}
				}
				else if (parent.getIncrement().equals("shl") || parent.getIncrement().equals("shr")) {
					boolean isLeft = parent.getIncrement().equals("shl");
					int thisIndex = parent.getMembers().indexOf(this);
					int lastDefinedIndex = thisIndex-1;
					while (lastDefinedIndex>=0 && parent.getMembers().get(lastDefinedIndex).getValue().length()==0) {
						--lastDefinedIndex;
					}
					Immediate lastDefined = (lastDefinedIndex>=0) ? parent.getMembers().get(lastDefinedIndex).getImmediate() : new Immediate(isLeft?1:0x80000000);
					lastDefinedIndex = Math.max(lastDefinedIndex,0);
					if (lastDefined==null || lastDefined.relocation!=null || lastDefined.diffRelocation!=null) {
						if (getValue().getNumErrors()==0) {
							getValue().addError(new SyntaxError(getValue(),0,getValue().length(),"The increment field is \""+parent.getIncrement()+", and the last defined value has an offset, which can't be shifted."));
						}
					}
					else {
						enumValue = new Immediate(isLeft ? (lastDefined.constant<<(thisIndex-lastDefinedIndex)) : (lastDefined.constant>>>(thisIndex-lastDefinedIndex)));
					}
				}
				else {
					if (parent.getIncrement().getNumErrors()==0) {
						parent.getIncrement().addError(new SyntaxError(parent.getIncrement(),0,parent.getIncrement().length(),"The increment field must be either a constant without offsets or \"shl\" or \"shr\"."));
					}
				}
			}
		}
		Object immediate = (enumValue==null) ? getValue().evaluate() : enumValue;
		Immediate oldValue = immediateValue;
		boolean isChanging;
		if (immediate instanceof Immediate) {
			isChanging = immediateValue==null || !immediateValue.equals(immediate);
			immediateValue = (Immediate)immediate;
		}
		else {
			isChanging = immediateValue!=null;
			immediateValue = null;
		}
		isEvaluating = false;
		if (isChanging) {
			Constants parent = getParent();
			if (parent!=null) {
				Scope scope = parent.getGlobalScope();
				if (scope!=null) {
					scope.symbolChanged(new ValueChangeEvent(this,oldValue,immediateValue));
				}
				// Check for if following values may need to be updated due to the increment in this enumeration
				if (parent.getIncrement().length()!=0) {
					int thisIndex = parent.getMembers().indexOf(this);
					// NOTE: Only need to update the next one, because if necessary, it will update the next one
					if (thisIndex+1<parent.getMembers().size() && parent.getMembers().get(thisIndex+1).getValue().length()==0) {
						parent.getMembers().get(thisIndex+1).evaluateIfNotAlready();
					}
				}
			}
		}
	}

	public void valueChanged(SingleLine line, String oldValue) {
		if (line==getValue()) {
			evaluateIfNotAlready();
		}
	}

	public void nowHaveGlobalScope() {
		evaluateIfNotAlready();
		if (!getParent().getFile().isExternal()) {
			getParent().getGlobalScope().addObserver(this);
		}
	}

	public void symbolAdded(Scope scope, Symbol symbol) {
		if (immediateValue==null) {
			evaluateIfNotAlready();
		}
	}

	public void symbolRemoved(Scope scope, Symbol symbol) {
		if (symbol==this) {
			scope.removeObserver(this);
		}
		else {
			Object[] references = getValue().getReferences();
			// Only reevaulate if necessary
			for (int i=0;i<getValue().getNumReferences();++i) {
				if (symbol==references[i]) {
					evaluateIfNotAlready();
					break;
				}
			}
		}
	}

	public void symbolChanged(Scope scope, SymbolChangeEvent e) {
		Symbol symbol = e.getSymbol();
		Object[] references = getValue().getReferences();
		boolean isReevaluated = false;
		// Only reevaulate if necessary
		for (int i=0;i<getValue().getNumReferences();++i) {
			if (symbol==references[i]) {
				evaluateIfNotAlready();
				isReevaluated = true;
				break;
			}
		}
		if (!isReevaluated && (symbol instanceof StructureMember || symbol instanceof Structure)) {
			Structure structure = (symbol instanceof StructureMember) ? ((StructureMember)symbol).getParent() : (Structure)symbol;
			for (int i=0;i<getValue().getNumReferences();++i) {
				if (references[i]==structure || (references[i] instanceof StructureMember && ((StructureMember)references[i]).getParent()==structure)) {
					evaluateIfNotAlready();
					break;
				}
			}
		}
	}
}
